﻿using System;
using System.Collections.Generic;
using System.Timers;
using System.Linq;
using GeneralDFS.Common;
using GeneralDFS.DataEntity.Enum;
using GeneralDFS.DataEntity.Nodes;

namespace GeneralDFS.BusinessLogical.Nodes
{
     [Serializable]
    public class DataNodeCenter
    {
        private readonly Dictionary<String, Queue<String>> _dataNodeCommands;
        private readonly Dictionary<String, DataNodeInfoWithBlocks> _dataNodeInfos;
        private readonly Dictionary<String, DataNodeInfoWithBlocks> _unreachDataNodeInfos;
        private readonly NameNode _nameNode;
        private readonly Timer _timerManageNodes;

        public DataNodeCenter(NameNode nameNode)
        {
            _nameNode = nameNode;
            _dataNodeInfos = new Dictionary<string, DataNodeInfoWithBlocks>();
            _unreachDataNodeInfos = new Dictionary<string, DataNodeInfoWithBlocks>();
            _dataNodeCommands = new Dictionary<string, Queue<string>>();
            _timerManageNodes = new Timer(60 * 10);
        }

        public Boolean RegisterDataNode(DataNodeInfoWithBlocks newNodeInfo)
        {
            if (_dataNodeInfos.ContainsKey(newNodeInfo.Name))
                return false;
            _dataNodeInfos.Add(newNodeInfo.Name, newNodeInfo);
            _dataNodeCommands.Add(newNodeInfo.Name, new Queue<string>());
            AddCommandToNode(newNodeInfo.Name, CommandType.UploadBlocks);
            return true;
        }

        /// <summary>
        /// 心跳更新
        /// </summary>
        public Boolean UpdateHeartBeat(String serverName, int rpcPort,int tcpPort ,String ip, long freeSpace, long usedSpace)
        {
            DataNodeInfoWithBlocks info;
            if (_dataNodeInfos.TryGetValue(serverName, out info))
            {
                info.RpcPort = rpcPort;
                info.TcpPort = tcpPort;
                info.LastUpdateTime = DateTime.Now.LongNow();
                info.FreeSpace = freeSpace;
                info.UsedSpace = usedSpace;
                info.State = DataNodeState.Alive;
                //todo 心跳时需要传入的其它参数
                return true;
            }
            if (_unreachDataNodeInfos.TryGetValue(serverName, out info))
            {
                info.RpcPort = rpcPort;
                info.LastUpdateTime = DateTime.Now.LongNow();
                info.FreeSpace = freeSpace;
                info.UsedSpace = usedSpace;
                info.State = DataNodeState.Alive;
                //todo 心跳时需要传入的其它参数
                return true;
            }
            return false;
        }

        /// <summary>
        /// 通过ServerName获取DataNode
        /// </summary>
        /// <param name="serverName"></param>
        /// <returns></returns>
        public DataNodeInfoWithBlocks GetDataNodeInfoByServerName(String serverName)
        {
            return _dataNodeInfos[serverName];
        }

        /// <summary>
        /// 通过DataNodeID获取DataNode
        /// </summary>
        /// <param name="dataNodeID"></param>
        /// <returns></returns>
        public DataNodeInfoWithBlocks GetDataNodeByID(DataNodeID dataNodeID)
        {
            if (_dataNodeInfos.ContainsKey(dataNodeID.Name))
            {
                var dataNode = _dataNodeInfos[dataNodeID.Name];
                if (dataNodeID.Ip == dataNode.Ip && dataNodeID.RpcPort == dataNode.RpcPort)
                    return dataNode;
            }
            RegisterDataNode(dataNodeID as DataNodeInfoWithBlocks);
            return GetDataNodeByID(dataNodeID);
        }

        /// <summary>
        /// 通过Ip获取DataNodeInfo列表
        /// </summary>
        /// <param name="ip"></param>
        /// <returns></returns>
        public List<DataNodeID> GetDataNodeInfoListByIp(String ip)
        {
            return (from dataNodeInfo in _dataNodeInfos
                    where dataNodeInfo.Value.Ip == ip
                    select dataNodeInfo.Value).ToList<DataNodeID>();
        }

        /// <summary>
        /// 获取所有有效节点
        /// </summary>
        /// <returns></returns>
        public List<DataNodeInfoWithBlocks> GetDataNodeInfoList()
        {
            return _dataNodeInfos.Values.ToList();
        }

        /// <summary>
        /// 有效节点的数量
        /// </summary>
        /// <returns></returns>
        public int AliveNodeCount()
        {
            return _dataNodeInfos.Count;
        }

        /// <summary>
        /// 不可达节点的数量
        /// </summary>
        /// <returns></returns>
        public int UnreachNodeCount()
        {
            return _unreachDataNodeInfos.Count;
        }

        #region commmand

        /// <summary>
        /// 添加命令到DataNode
        /// </summary>
        /// <param name="serverName"></param>
        /// <param name="command"></param>
        public void AddCommandToNode(String serverName, String command)
        {
            if (!_dataNodeCommands.ContainsKey(serverName))
                _dataNodeCommands.Add(serverName, new Queue<String>());
            Queue<String> commands = _dataNodeCommands[serverName];
            if (commands == null)
                commands = new Queue<string>();

            commands.Enqueue(command);
        }

        /// <summary>
        /// 通过ServerName获取Command
        /// </summary>
        /// <returns></returns>
        public String GetNodeCommandByServerName(String serverName)
        {
            if (serverName == null) throw new ArgumentNullException("serverName");
            if (!_dataNodeCommands.ContainsKey(serverName) || _dataNodeCommands[serverName] == null)
                return null;

            Queue<String> commands = _dataNodeCommands[serverName];
            if (commands.Count > 0)
                return commands.Dequeue();

            return null;
        }
        #endregion

        public void Start()
        {
            _timerManageNodes.Elapsed += timerManageNodes_Elapsed;
            _timerManageNodes.Start();
        }

        public void Stop()
        {
            _timerManageNodes.Stop();
        }

        void timerManageNodes_Elapsed(object sender, ElapsedEventArgs e)
        {
            if(_nameNode.LocalNode.GetNodeState()==NameNodeState.SafeMode)
                return;
            const int spanTime = 10;
            foreach (var dataNodeInfo in _dataNodeInfos)
            {
                var lastUpdateTime = DateTime.FromFileTime(dataNodeInfo.Value.LastUpdateTime);
                var timeSpan = lastUpdateTime.Subtract(DateTime.Now);
                if (!(timeSpan.TotalSeconds > spanTime)) continue;
                lock (_dataNodeInfos)
                {
                    dataNodeInfo.Value.State = DataNodeState.UNormal;
                    _unreachDataNodeInfos.Add(dataNodeInfo.Key, dataNodeInfo.Value);
                    _dataNodeInfos.Remove(dataNodeInfo.Key);
                    LogUtil.LogInfo(String.Format("DataNode ServerName:{0} down", dataNodeInfo.Value.Name));
                }
            }

            foreach (var dataNodeInfo in _unreachDataNodeInfos)
            {
                var lastUpdateTime = DateTime.FromFileTime(dataNodeInfo.Value.LastUpdateTime);
                var timeSpan = lastUpdateTime.Subtract(DateTime.Now);
                if (!(timeSpan.TotalSeconds < spanTime)) continue;
                lock (_dataNodeInfos)
                {
                    dataNodeInfo.Value.State = DataNodeState.Alive;
                    _dataNodeInfos.Add(dataNodeInfo.Key, dataNodeInfo.Value);
                    _unreachDataNodeInfos.Remove(dataNodeInfo.Key);
                    LogUtil.LogInfo(String.Format("DataNode ServerName:{0} up", dataNodeInfo.Value.Name));

                }
            }
        }

        public void CopyToDataNode(DataEntity.IO.FileBlockInfo block, int copyTimes)
        {
            for (int i = 0; i < copyTimes; i++)
            {
                var newNode = block.GetDataNodeInfo(0);
                while (block.FindDataNodeInfo(newNode) <0)
                {
                    var intRandom=new Random();
                    newNode = _dataNodeInfos.ElementAt(intRandom.Next(_dataNodeInfos.Count)).Value;
                }
                //todo 添加复制文件块命令
                AddCommandToNode(block.GetDataNodeInfo(0).Name,"");
            }
        }

        public DataNodeID GetNodeID()
        {
            var random = new Random();
            var index = random.Next(_dataNodeInfos.Values.Count()-1 );
            return _dataNodeInfos.Values.ToList()[index];
        }
    }
}